<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <input type="text" v-model="text">
    <p>{{text}}</p>
    <button @click="add">+</button>
  </div>

  <script>
    class Logger{
      constructor(){

      }

      static log(msg, value){
        console.log(`%c [${msg} ] %O`,'color:red;',value)
      }
    }
   class Vue{
     //构造
    constructor(options = {}){
      this.$el = document.querySelector(options.el)
      Logger.log('options',options)
      this.data = options.data
      let data = options.data
      Object.keys(data).forEach((key) => {
        this.proxyData(key)
      })
      this.methods = options.methods  //时间方法
      this.watcherTask = {}  //需要监听的任务列表
      this.observer(data)   //初始化劫持监听所有数据
      this.compile(this.$el)
    }

    //数据代理
    proxyData(key){
      let self = this
      Object.defineProperty(self, key, {
        configurable: false,
        enumerable: true,
        get(){
          return self.data[key]
        },
        set(newVal){
          self.data[key] = newVal
        }
      })
    }

    //订阅者——劫持监听所有数据
    observer(data){
      let self = this
      Object.keys(data).forEach(key => {
        let value = data[key]
        self.watcherTask[key] = []
        Object.defineProperty(data, key, {
          configurable: false,
          enumerable: true,
          get(){
            return value
          },
          set(newValue){
            if(newValue !== value){
              console.error(1)
              value = newValue
              self.watcherTask[key].forEach(task => {
                task.update()
              })
            }
          }
        })
      })
    }

    //解析dom
    compile(el){
      let nodes = el.childNodes
      let self = this
        
      for(let i = 0;i<nodes.length;i++){
        const node = nodes[i]
        if(node.nodeType === 3){
          let text = node.textContent.trim()
          if(!text) continue
          self.compileText(node,'textContent')
        } else if(node.nodeType === 1){
          if(node.childNodes.length > 0){
            self.compile(node)
          }
          if(node.hasAttribute('v-model') && (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA')){
            let attrVal = node.getAttribute('v-model')
            node.value = self.data[attrVal]
              self.watcherTask[attrVal].push(new Watcher(node, self, attrVal, 'value'))
              node.removeAttribute('v-model')
            node.addEventListener('input', () => {
              Logger.log('watcherTask',self.watcherTask[attrVal])
              self.data[attrVal] = node.value
            })
          }
          if(node.hasAttribute('v-html')){
            let attrVal = node.getAttribute('v-html')
            this.watcherTask[attrVal].push(new Watcher(node,this,attrVal,'innerHTML'))
            node.removeAttribute('v-html')
          }
          this.compileText(node,'innerHTML')
          if(node.hasAttribute('@click')){
            let attrVal = node.getAttribute('@click')
            node.removeAttribute('@click')
            node.addEventListener('click',e => {
              this.methods[attrVal] && this.methods[attrVal].bind(this)()
            })
          }
        }
      }
    }

    //解析dom里处理纯双花括号的操作
    compileText(node, type){
      let reg = /\{\{(.*)\}\}/g, txt = node.textContent;
      if(reg.test(txt)){
        node.textContent = txt.replace(reg,(matched,value)=>{
          let tpl = this.watcherTask[value] || []
          tpl.push(new Watcher(node,this,value,type))
          return value.split('.').reduce((val, key) => {
              return this.data[key]; 
          }, this.$el);
        })
      }
    }
   }

    //发布者——更新视图操作
   class Watcher{
    constructor(el,vm,value,type){
      this.el = el;
      this.vm = vm;
      this.value = value;
      this.type = type;
      this.update()
    }
    update(){
      this.el[this.type] = this.vm.data[this.value]
    }
   }

   new Vue({
     el:'#app',
     data: {
       text: 0
     },
     methods:{
       add(){
         this.text++  
       }
     }
   })
  </script>
</body>
</html>